feat(tasks-panel): move task and automation filters server-side#3173
feat(tasks-panel): move task and automation filters server-side#3173
Conversation
Task member/type filters now drive useTasks query params instead of filtering already-fetched data in the component. Automation name search uses an ILIKE query via the AUTOMATION_LIST tool instead of client-side array filter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsSuggested: Minor ( React with an emoji to override the release type:
Current version:
|
myTasks was hardcoded to owner:"me" so in "All members" mode manual tasks still only showed the current user's chats. Both queries now share taskOwner derived from memberFilter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-task-filters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
24 issues found across 343 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/cli/lib/clipboard.ts">
<violation number="1" location="apps/mesh/src/cli/lib/clipboard.ts:30">
P2: Handle `child.stdin` errors before writing so clipboard failures cannot surface as unhandled stream errors.</violation>
</file>
<file name="apps/mesh/src/web/components/chat/simple-mode-tier-dropdown.tsx">
<violation number="1" location="apps/mesh/src/web/components/chat/simple-mode-tier-dropdown.tsx:47">
P2: The dropdown trigger has no accessible name on small screens because its label text is hidden; add an `aria-label` to keep it screen-reader accessible at all breakpoints.</violation>
</file>
<file name="apps/docs/client/src/components/ui/ProductSwitcher.tsx">
<violation number="1" location="apps/docs/client/src/components/ui/ProductSwitcher.tsx:24">
P2: `useEffect` is banned in this repository, so this hook usage should be refactored to an allowed pattern (or explicitly justified with a lint disable if truly unavoidable).
(Based on your team's feedback about not using useEffect in this codebase.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/src/api/utils/stream-guard.ts">
<violation number="1" location="apps/mesh/src/api/utils/stream-guard.ts:58">
P2: Do not forward `Content-Length` unchanged when wrapping a stream that may close early; it can advertise an incorrect body size after truncation.</violation>
</file>
<file name=".github/workflows/release-studio-sandbox.yaml">
<violation number="1" location=".github/workflows/release-studio-sandbox.yaml:58">
P1: Do not gate this path-scoped image workflow on pre-existing image tags; it can skip publishing after real sandbox changes.
(Based on your team's feedback about not skipping path-scoped image builds when a tag already exists.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/src/web/components/thread/github/decode-html-entities.ts">
<violation number="1" location="apps/mesh/src/web/components/thread/github/decode-html-entities.ts:24">
P2: Numeric entity decoding can throw on out-of-range code points; guard the parsed value before calling `String.fromCodePoint`.</violation>
</file>
<file name="apps/mesh/src/web/hooks/use-automations.ts">
<violation number="1" location="apps/mesh/src/web/hooks/use-automations.ts:167">
P2: Normalize `search` in the query key to match request semantics; currently empty/null search values create different cache keys even though the request sent is identical.</violation>
</file>
<file name="apps/mesh/src/web/components/thread/github/comments-accordion.tsx">
<violation number="1" location="apps/mesh/src/web/components/thread/github/comments-accordion.tsx:42">
P1: Do not decode GitHub comment bodies before rendering markdown; it can turn escaped HTML into executable markup.
(Based on your team's feedback about avoiding HTML-entity decoding before MemoizedMarkdown for PR comments.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/migrations/071-default-home-agents.ts">
<violation number="1" location="apps/mesh/migrations/071-default-home-agents.ts:3">
P1: This migration is not registered in the migrations index, so it will never be executed.</violation>
</file>
<file name="apps/mesh/src/api/middleware/log-deprecated-route.ts">
<violation number="1" location="apps/mesh/src/api/middleware/log-deprecated-route.ts:21">
P2: New middleware is not wired into runtime routes, so deprecation logging never runs outside tests.</violation>
</file>
<file name="apps/mesh/src/shared/github-clone-info.ts">
<violation number="1" location="apps/mesh/src/shared/github-clone-info.ts:30">
P2: This new shared `buildCloneInfo` function is currently unused, leaving dead code and duplicated clone-info logic in `tools/vm/start.ts`.</violation>
</file>
<file name="apps/mesh/src/web/components/chat/tiptap/build-improve-prompt-doc.ts">
<violation number="1" location="apps/mesh/src/web/components/chat/tiptap/build-improve-prompt-doc.ts:27">
P2: Escape `instructions` before inserting it into `<current_instructions>...</current_instructions>` so embedded closing tags cannot break the prompt structure.</violation>
</file>
<file name="apps/mesh/src/web/components/thread/github/use-pr-reviews.ts">
<violation number="1" location="apps/mesh/src/web/components/thread/github/use-pr-reviews.ts:70">
P2: `unresolvedConversations` is computed from total review comment count, which is not an unresolved-thread signal and can produce incorrect review status.</violation>
</file>
<file name="apps/mesh/src/web/components/thread/github/description-tab.tsx">
<violation number="1" location="apps/mesh/src/web/components/thread/github/description-tab.tsx:35">
P0: Do not decode HTML entities before rendering PR body markdown; this can turn escaped user content into executable/raw HTML.
(Based on your team's feedback about not decoding GitHub bodies before MemoizedMarkdown with rehype-raw.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/migrations/072-ai-provider-key-preset-id.ts">
<violation number="1" location="apps/mesh/migrations/072-ai-provider-key-preset-id.ts:3">
P1: Register this new migration in `migrations/index.ts`; otherwise `preset_id` is never created and code expecting that column can fail at runtime.
(Based on your team's feedback about checking migration registration in migrations/index.ts before flagging.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/migrations/075-thread-inflight-async-jobs.ts">
<violation number="1" location="apps/mesh/migrations/075-thread-inflight-async-jobs.ts:3">
P1: This migration is not registered in `migrations/index.ts`, so it will never run.
(Based on your team's feedback about verifying migration registration in migrations/index.ts.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/migrations/070-model-categories.ts">
<violation number="1" location="apps/mesh/migrations/070-model-categories.ts:6">
P1: This migration duplicates `068-model-categories` by adding/dropping the same `simple_mode` column, which can cause migration failure when applying later migrations.</violation>
</file>
<file name="apps/mesh/migrations/073-backfill-basic-usage-roles.ts">
<violation number="1" location="apps/mesh/migrations/073-backfill-basic-usage-roles.ts:19">
P1: Migration not registered in `migrations/index.ts`. Without an import and map entry (e.g., `import * as migration073backfillbasicusageroles from './073-backfill-basic-usage-roles.ts'` and `"073-backfill-basic-usage-roles": migration073backfillbasicusageroles`), Kysely's `Migrator` will never execute this file.
(Based on your team's feedback about checking migration registration in index.ts.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/src/web/components/settings/settings-section.tsx">
<violation number="1" location="apps/mesh/src/web/components/settings/settings-section.tsx:108">
P2: Guard the row key handler so Enter/Space only activates when the row itself has focus; otherwise nested action controls can trigger the parent click via keyboard bubbling.</violation>
</file>
<file name="apps/mesh/src/api/routes/thread-outputs.ts">
<violation number="1" location="apps/mesh/src/api/routes/thread-outputs.ts:57">
P2: This endpoint truncates thread outputs at 200 files because it reads only the first storage page and ignores continuation tokens.</violation>
</file>
<file name="apps/mesh/src/api/routes/vm-exec.ts">
<violation number="1" location="apps/mesh/src/api/routes/vm-exec.ts:68">
P2: Client request body is discarded. The proxy sends `body: null` to the daemon regardless of what the client POSTs. If the daemon's exec endpoint accepts parameters (arguments, env vars, etc.), they will be silently lost. Consider forwarding the body: `body: await c.req.raw.clone().body`.</violation>
</file>
<file name="apps/mesh/src/api/middleware/resolve-org-from-path.ts">
<violation number="1" location="apps/mesh/src/api/middleware/resolve-org-from-path.ts:84">
P1: Object storage is only rebuilt when null, but the middleware unconditionally overrides every other org-scoped field. If `ctx.objectStorage` was already constructed for a different org (e.g., from an active session), it will remain bound to the wrong org while all other context fields point to the path-resolved org. Consider always rebuilding it (matching the unconditional `setOrganizationId` pattern used for threads storage) to prevent cross-org storage access if preconditions change.</violation>
</file>
<file name="apps/mesh/src/web/hooks/use-org-auth-client.ts">
<violation number="1" location="apps/mesh/src/web/hooks/use-org-auth-client.ts:86">
P2: `getFullOrganization` defaults to the session's active organization when no `organizationId` is passed (per Better Auth docs), making it vulnerable to the same cross-tab issue this hook is designed to prevent. It should be wrapped with `withQueryOrgId` rather than passed through directly.</violation>
</file>
<file name="apps/mesh/migrations/069-sandbox-runner-state.ts">
<violation number="1" location="apps/mesh/migrations/069-sandbox-runner-state.ts:19">
P2: Use a SQL expression for the timestamp default; `defaultTo("now()")` is treated as a string literal, not a `now()` function call.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
| <div className="text-sm"> | ||
| <MemoizedMarkdown | ||
| id={`pr-body-${pr.number}`} | ||
| text={decodeHtmlEntities(pr.body)} |
There was a problem hiding this comment.
P0: Do not decode HTML entities before rendering PR body markdown; this can turn escaped user content into executable/raw HTML.
(Based on your team's feedback about not decoding GitHub bodies before MemoizedMarkdown with rehype-raw.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/components/thread/github/description-tab.tsx, line 35:
<comment>Do not decode HTML entities before rendering PR body markdown; this can turn escaped user content into executable/raw HTML.
(Based on your team's feedback about not decoding GitHub bodies before MemoizedMarkdown with rehype-raw.) </comment>
<file context>
@@ -0,0 +1,46 @@
+ <div className="text-sm">
+ <MemoizedMarkdown
+ id={`pr-body-${pr.number}`}
+ text={decodeHtmlEntities(pr.body)}
+ />
+ </div>
</file context>
| fi | ||
|
|
||
| - name: Setup Bun | ||
| if: steps.tag-check.outputs.exists != 'true' |
There was a problem hiding this comment.
P1: Do not gate this path-scoped image workflow on pre-existing image tags; it can skip publishing after real sandbox changes.
(Based on your team's feedback about not skipping path-scoped image builds when a tag already exists.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release-studio-sandbox.yaml, line 58:
<comment>Do not gate this path-scoped image workflow on pre-existing image tags; it can skip publishing after real sandbox changes.
(Based on your team's feedback about not skipping path-scoped image builds when a tag already exists.) </comment>
<file context>
@@ -0,0 +1,124 @@
+ fi
+
+ - name: Setup Bun
+ if: steps.tag-check.outputs.exists != 'true'
+ uses: oven-sh/setup-bun@v2
+ with:
</file context>
| </div> | ||
| <MemoizedMarkdown | ||
| id={`pr-comment-${c.id}`} | ||
| text={decodeHtmlEntities(c.body)} |
There was a problem hiding this comment.
P1: Do not decode GitHub comment bodies before rendering markdown; it can turn escaped HTML into executable markup.
(Based on your team's feedback about avoiding HTML-entity decoding before MemoizedMarkdown for PR comments.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/components/thread/github/comments-accordion.tsx, line 42:
<comment>Do not decode GitHub comment bodies before rendering markdown; it can turn escaped HTML into executable markup.
(Based on your team's feedback about avoiding HTML-entity decoding before MemoizedMarkdown for PR comments.) </comment>
<file context>
@@ -0,0 +1,64 @@
+ </div>
+ <MemoizedMarkdown
+ id={`pr-comment-${c.id}`}
+ text={decodeHtmlEntities(c.body)}
+ />
+ </div>
</file context>
| text={decodeHtmlEntities(c.body)} | |
| text={c.body} |
| @@ -0,0 +1,15 @@ | |||
| import { Kysely } from "kysely"; | |||
|
|
|||
| export async function up(db: Kysely<unknown>): Promise<void> { | |||
There was a problem hiding this comment.
P1: This migration is not registered in the migrations index, so it will never be executed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/migrations/071-default-home-agents.ts, line 3:
<comment>This migration is not registered in the migrations index, so it will never be executed.</comment>
<file context>
@@ -0,0 +1,15 @@
+import { Kysely } from "kysely";
+
+export async function up(db: Kysely<unknown>): Promise<void> {
+ await db.schema
+ .alterTable("organization_settings")
</file context>
| @@ -0,0 +1,15 @@ | |||
| import { type Kysely } from "kysely"; | |||
|
|
|||
| export async function up(db: Kysely<unknown>): Promise<void> { | |||
There was a problem hiding this comment.
P1: Register this new migration in migrations/index.ts; otherwise preset_id is never created and code expecting that column can fail at runtime.
(Based on your team's feedback about checking migration registration in migrations/index.ts before flagging.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/migrations/072-ai-provider-key-preset-id.ts, line 3:
<comment>Register this new migration in `migrations/index.ts`; otherwise `preset_id` is never created and code expecting that column can fail at runtime.
(Based on your team's feedback about checking migration registration in migrations/index.ts before flagging.) </comment>
<file context>
@@ -0,0 +1,15 @@
+import { type Kysely } from "kysely";
+
+export async function up(db: Kysely<unknown>): Promise<void> {
+ await db.schema
+ .alterTable("ai_provider_keys")
</file context>
| onKeyDown={ | ||
| onClick | ||
| ? (e) => { | ||
| if (e.key === "Enter" || e.key === " ") { |
There was a problem hiding this comment.
P2: Guard the row key handler so Enter/Space only activates when the row itself has focus; otherwise nested action controls can trigger the parent click via keyboard bubbling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/components/settings/settings-section.tsx, line 108:
<comment>Guard the row key handler so Enter/Space only activates when the row itself has focus; otherwise nested action controls can trigger the parent click via keyboard bubbling.</comment>
<file context>
@@ -0,0 +1,166 @@
+ onKeyDown={
+ onClick
+ ? (e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onClick();
</file context>
| } | ||
| const result = await storage.list({ | ||
| prefix: `model-outputs/${threadId}/`, | ||
| maxKeys: 200, |
There was a problem hiding this comment.
P2: This endpoint truncates thread outputs at 200 files because it reads only the first storage page and ignores continuation tokens.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/thread-outputs.ts, line 57:
<comment>This endpoint truncates thread outputs at 200 files because it reads only the first storage page and ignores continuation tokens.</comment>
<file context>
@@ -0,0 +1,85 @@
+ }
+ const result = await storage.list({
+ prefix: `model-outputs/${threadId}/`,
+ maxKeys: 200,
+ });
+
</file context>
| upstream = await runner.proxyDaemonRequest(claimName, daemonPath, { | ||
| method: "POST", | ||
| headers: new Headers(), | ||
| body: null, |
There was a problem hiding this comment.
P2: Client request body is discarded. The proxy sends body: null to the daemon regardless of what the client POSTs. If the daemon's exec endpoint accepts parameters (arguments, env vars, etc.), they will be silently lost. Consider forwarding the body: body: await c.req.raw.clone().body.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/vm-exec.ts, line 68:
<comment>Client request body is discarded. The proxy sends `body: null` to the daemon regardless of what the client POSTs. If the daemon's exec endpoint accepts parameters (arguments, env vars, etc.), they will be silently lost. Consider forwarding the body: `body: await c.req.raw.clone().body`.</comment>
<file context>
@@ -0,0 +1,98 @@
+ upstream = await runner.proxyDaemonRequest(claimName, daemonPath, {
+ method: "POST",
+ headers: new Headers(),
+ body: null,
+ });
+ } catch (err) {
</file context>
| // Cross-org or pre-org operations. | ||
| list: authClient.organization.list, | ||
| create: authClient.organization.create, | ||
| getFullOrganization: authClient.organization.getFullOrganization, |
There was a problem hiding this comment.
P2: getFullOrganization defaults to the session's active organization when no organizationId is passed (per Better Auth docs), making it vulnerable to the same cross-tab issue this hook is designed to prevent. It should be wrapped with withQueryOrgId rather than passed through directly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/hooks/use-org-auth-client.ts, line 86:
<comment>`getFullOrganization` defaults to the session's active organization when no `organizationId` is passed (per Better Auth docs), making it vulnerable to the same cross-tab issue this hook is designed to prevent. It should be wrapped with `withQueryOrgId` rather than passed through directly.</comment>
<file context>
@@ -0,0 +1,89 @@
+ // Cross-org or pre-org operations.
+ list: authClient.organization.list,
+ create: authClient.organization.create,
+ getFullOrganization: authClient.organization.getFullOrganization,
+ },
+ };
</file context>
| .addColumn("handle", "text", (col) => col.notNull()) | ||
| .addColumn("state", "jsonb", (col) => col.notNull()) | ||
| .addColumn("updated_at", "timestamptz", (col) => | ||
| col.notNull().defaultTo("now()"), |
There was a problem hiding this comment.
P2: Use a SQL expression for the timestamp default; defaultTo("now()") is treated as a string literal, not a now() function call.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/migrations/069-sandbox-runner-state.ts, line 19:
<comment>Use a SQL expression for the timestamp default; `defaultTo("now()")` is treated as a string literal, not a `now()` function call.</comment>
<file context>
@@ -0,0 +1,38 @@
+ .addColumn("handle", "text", (col) => col.notNull())
+ .addColumn("state", "jsonb", (col) => col.notNull())
+ .addColumn("updated_at", "timestamptz", (col) =>
+ col.notNull().defaultTo("now()"),
+ )
+ .addPrimaryKeyConstraint("sandbox_runner_state_pkey", [
</file context>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What is this contribution about?
Task member/type filters in the tasks panel were filtering already-fetched data client-side in
TasksSection. Automation name search in the automations list was also a client-side array filter. Both are now server-side.memberFilter("mine"/"all") now drives theownerparam of the automation tasks query;typeFilter("all"/"manual"/"automation") controls which server result-sets are included. Filter state lifted fromTasksSectiontoTasksPanelContent.AUTOMATION_LISTtool now accepts asearchparam, passed as an ILIKE condition inlistWithTriggerCounts.useAutomationshook threads the param through;automations-list.tsxusesuseTransitionfor a responsive input while the query refetches.Screenshots/Demonstration
No visual change — behavior is identical, filtering now happens at the DB/query level.
How to Test
searchargument rather than filtering the full list client-side.Review Checklist
Summary by cubic
Moved task/member/type filters to the server so we only fetch needed data, and added server-side search for automations. Fixed “All members” so it applies to both manual and automation queries; no UI changes.
Refactors
memberFilterdrivesownerinuseTasks("me"/"all");typeFilterselects result sets; filter state lifted toTasksPanelContentwithuseTransition;TasksSectionrenders server results (no local filtering).AUTOMATION_LISTand storage accept optionalsearchand applyILIKEon names;useAutomationsthreadssearchwithKEYS.automations;automations-list.tsxuses a transition-backed search input.Bug Fixes
ownerfrommemberFilter.Written for commit 46c1390. Summary will update on new commits.